←前へ | ホーム | 次へ→ |
3 章では,本特集で作成するCD 試聴機の機能のうち,タッチパネルディスプレイを使って画像を表示する部分の制作手順を解説していきます.利用するタッチパネル付きディスプレイは,Quixun「QT-701AV-S」です.
この章ではタッチパネル付きディスプレイを使用して,簡易画像ビューアを作成します.プログ ラムの仕様ですが,JPEG画像のあるディレクトリを指定してプログラムを実行し,タッチパネル上 の右/左矢印を選択すると,指定したディレクトリ内の画像ファイルが切り替わって表示されるも のとします(図1)
Quixun「QT-701AV-S」をタッチパネル付きディスプレイとして使用します(写真1).これは,本誌2005 年4 月号の「Armadillo-9 で卓上ハードディスクプレーヤを作る(3)」で使用したものと同じものです.
●写真1 Quixun「QT-701AV-S」最大でXGA(1024 × 768)が表示でき,タッチパネルのインターフェースはUSBです.組み込み向けの周辺機器は少数では購入できないなど,入手が難しいものが多いですが,このディスプレイは入手性が良いので重宝します.最初にArmadillo-9のVGAとUSBにコネクタを接続してください(写真2)
●写真2 Armadillo-9と接続したところまず画像表示機能に関する説明をします.
表示する画像のフォーマットはJPEG とします.JPEGのライブラリには,有名なフリーのライブラリ「IJG JPEG Library」(http://www.ijg.org/)を使用しました.
開発環境にARM用のライブラリを追加する必要がありますが,幸いatmark-distにはJPEGライブラリのソースコードが含まれているので,これをコンフィギュレーションしてコンパイルします.ライブラリ用のメニューを表示して,libjpeg をビルドする設定にします(図2).
●図2 libjpeg をビルドするよう設定
Library Configuration ---> [*] Build libjpeg
ビルドを行ってlibjpegを作成します(図3).
●図3 libjpeg を作成$ make lib_only … $ ls lib/libjpeg/*.a lib/libjpeg/libjpeg.a $
フレームバッファデバイスとは,グラフィックハードウェアを抽象化したものです.ビデオのハードウェアに依存せず,規定されたインターフェースでアプリケーションから操作できるので,下位レベルのインターフェースについて知る必要がなくなります.
今回の画像の表示処理ではフレームバッファデバイスに直接データを書き込みます.簡単な描画であれば,X Window SystemやQtなどといったウィンドウシステムのライブラリを使うより軽快に動作する点と,実行ファイルのサイズが小さくて済むといった,組み込み向けのシステムには魅力的な利点があります.
Armadillo-9のLinuxカーネルはデフォルトでフレームバッファが有効になっており,/dev/fb0 にデータを書き込むことで画面の表示を操作することができます.
通常プログラムでは,mmap システムコールでフレームバッファをメモリにマッピングして操作します.
デフォルトのフレームバッファの設定では,画面のサイズが640×480/16ビットですが,今回は800 × 600 / 24 ビットに変更して使用します.このため,ブートローダに設定されているカーネルの起動オプションを変更します.Armadillo-9のジャンパ2をショート(ジャンパが挿さっている)させて電源を入れ直すとブートローダーのプロンプトが表示されるので,図4のように入力します.
変更が終わったらジャンパ2をオープンにしておきます.
●図4 フレームバッファの設定hermit> setenv video=ep93xxfb:CRT-800x600,24bpp console=ttyAM0,115200 hermit> setenv 1: video=ep93xxfb:CRT-800x600,24bpp 2: console=ttyAM0,115200 hermit>
リスト1のサンプルは,指定したJPEGファイルを読み込んで,サイズ分の領域を確保したバッファに24 ビットのRGB データを書き込む関数です.おもにlibjpeg の関数を呼び出しています.
●リスト1 JPEG ファイルを読み込み,バッファにRGB データを書き込む(ソースコード)
/***********************************************************************
* summary : JPEGライブラリ内でエラーが発生したときに呼ばれる関数
* エラーメッセージを表示した後,longjmp でsetjmp 位置へ
* ジャンプする
* return : -
**********************************************************************/
METHODDEF(void) error_exit(j_common_ptr cinfo)
{
error_ptr err = (error_ptr)cinfo->err;
(*cinfo->err->output_message)(cinfo);
longjmp(err->setjmp_buffer, 1);
}
|
(1) |
/*********************************************************************** * summary : JPEGファイルを読み込んでRGB24ビットのデータに変換する関数 * libjpegを利用(プロジェクトのlibjpeg.docを参照して作成) * return : 0 成功, -1 失敗 **********************************************************************/ int convert_jpegfile( window_item* jpeg_picture /* 矩形画像用の構造体*/ ) { int row_stride; int pos = 0; JSAMPARRAY buffer; struct jpeg_decompress_struct cinfo; struct error_manager jerr; FILE* infile; printf("JPEG file : %s\n", jpeg_picture->filename); /** * JPEGファイルをオープンする **/ if((infile = fopen(jpeg_picture->filename, "rb")) == NULL){ fprintf(stderr, "can't open %s\n", jpeg_picture->filename); return -1; } |
|
/**
* JPEGライブラリの関数内でエラーが発生した時に呼び出される
* エラーハンドラを登録し,setjmp で戻る位置を設定する
* (エラーハンドラを登録しないと,エラー発生時にライブラリ内でexit() が
* コールされ,勝手にプログラムが終了してしまう)
**/
cinfo.err = jpeg_std_error(&jerr);
jerr.pub.error_exit = error_exit;
if(setjmp(jerr.setjmp_buffer)){
jpeg_destory_decompress(&cinfo);
fclose(infile);
return -1;
}
|
(2) |
/**
* JPEGファイルから,JPEGデータを読み出してデコードし,
* RGBデータに変換して,バッファに格納する処理
**/
|
(3) |
/* ファイルを変換ソースとして設定し,ヘッダ部分を読み取ってデコードを開始する*/ jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, infile); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); |
  |
/* 1ラインのデータサイズを計算*/
row_stride = cinfo.output_width * cinfo.output_components;
/* 変換用バッファと,変換後のデータを格納するバッファの確保*/
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
jpeg_picture->data = malloc(row_stride * cinfo.output_height);
if(jpeg_picture->data == NULL){
fprintf(stderr, "malloc() failed\n");
return -1;
}
|
(4) |
/* !!!画像の1ラインごとJPEGデータをスキャンして,RGBデータとしてをコピー*/
pos = 0;
while(cinfo.output_scanline < cinfo.output_height){
jpeg_read_scanlines(&cinfo, buffer, 1);
memcpy(jpeg_picture->data + pos, buffer[0], row_stride);
pos += row_stride;
}
|
(5) |
/**
* JPEGの画像サイズを保存
**/
jpeg_picture->width = cinfo.output_width;
jpeg_picture->height = cinfo.output_height;
jpeg_picture->depth = cinfo.output_components;
/**
* 使用したリソースの開放
**/
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
}
|
(6) |
JPEGライブラリ関数内でエラーが発生した場合に呼び出される関数です.エラーメッセージを表示した後,longjmpでsetjmp位置にジャンプさせています.
容易したエラー関数をJPEGライブラリのエラーハンドラとして登録し,setjmp でハンドラから戻る位置を設定しています.自分でエラー関数を準備しなくても良いのですが,この場合JPEGライブラリ内でエラーが発生すると,エラーメッセージを出力した後,ライブラリ内部でexit()がコールされてプログラムが勝手に終了してしまいます.
JPEGファイルを読み込み,RGBデータに変換する処理の開始部分です.JPEGライブラリにファイルのデータを渡して,ヘッダ解析を行わせた後,変換を開始します.
変換後の画像1ライン分のデータサイズを解散した後,変換用のバッファ領域と,変換後のデータを格納するバッファ領域を確保します.
while のルーチンを用いて,1 ラインずつ変換後のデータを確保した領域に格納していきます.
のちほど描画関数に渡すための画像のサイズをコピーした後,ライブラリの後始末をしています.
次に,フレームバッファデバイスを扱っている部分です(リスト2).通常フレームバッファは,デバイスをオープンした後,mmap でメモリにマップして使用します.リスト2では,最初に真っ黒に塗りつぶすために0の値でマップしたメモリを埋めています.使用を終えたらunmmap でマッピングを解除します.
●リスト2 フレームバッファを扱う部分/** * フレームバッファデバイス(/dev/fb0)のオープン **/ fb_fd = open(FB_DEV_NAME, O_RDWR); if(fb_fd < 0){ perror(FB_DEV_NAME); return 0; } <省略> /** * フレームバッファをメモリマッピングし,画面を初期化する(真っ黒) **/ fb_memory = mmap(0, FB_SIZE, PROT_WRITE, MAP_SHARED, fb_fd, 0); if(fb_memory < 0){ printf("mmap() failed\n"); return 0; } memset(fb_memory, 0, FB_SIZE); <省略> /** * 使用終了後,メモリマッピングを解除 **/ unmmap(fb_memory); close(fb_fd);
ディスプレイを使用する組み込み機器で入力装置が必要な場合,最初に思い浮かぶのはタッチパネルだと思います.実際,世の中の組み込み機器において,タッチパネルが利用されている例は数え切れないでしょう.
当初,入力デバイスには操作が直感的なUSBジョイスティックを利用することも考えましたが,より少ない周辺機器でシンプルにシステムを作成するためタッチパネルを利用することにしました.
linuxのinputデバイスレイヤにはタッチパネル用のインターフェースも準備されており,デバイスファイル/dev/input/tsXでアクセスできます.タッチイベントが発生したときに,read システムコールを呼ぶことで,イベントの内容を読み取ることができます.
イベント発生時に通知されるデータフォーマットを表に記載します.
●表 タッチパネルイベントのデータフォーマット時刻 | 種類 | コード | パラメータ |
8バイト | 2バイト | 2バイト | 4バイト |
実際にタッチパネルを触ってみて,イベントで通知されるデータを確認してみます(図5).今回使用するタッチパネルでは,押す強さまでは取得できないようです.
●図5 通知されるデータの確認# mkdir /dev/input # mknod /dev/input/ts0 c 13 128 # cat /dev/input/ts0 | od -x
カーネルのメニューを開いて,タッチパネル用のドライバとインプットレイヤのタッチパネルインターフェースを有効にします(図6).
●図6 タッチパネルのドライバを追加するDevice Drivers ---> Input device support ---> <*> Touchscreen interface (800) Horizontal screen resolution (600) Vertical screen resolution USB support ---> <*> USB Human Interface Device (full HID) support [*] HID input layer suport <*> eGalax Touchkit USB Touchscreen Deiver
まずタッチパネルのイベント読み込む関数です(リスト3).リリースイベント(指が離れる)が発生したときに,それ以前に通知された座標イベントの値を返す作りにしました.
●リスト3 タッチパネルのイベント読み取り(ソースコード)
/*
* タッチパネルのイベントのデータフォーマット
*/
struct touchpanel_event
{
short pressure;
short x;
short y;
short millisecs;
};
|
(1) |
/*********************************************************************** * summary : タッチパネルのリリースイベント発生と,その位置を返す関数 * 座標の通知イベントで通知されたxとy座標を保持しておき * リリースイベントが発生したときにその値を返す仕様 * return : リリースイベント:0, その他のイベントの場合:-1 **********************************************************************/ int touchpanel_event( int fd, /* タッチパネルデバイスのファイルディスクリプタ*/ int *x, /* x 座標*/ int *y /* y 座標*/ ) |
|
/**
* タッチパネルのイベントを読み込む
**/
size = read(fd, &event_data, sizeof(event_data));
if(size < 0){
perror("touchpanel read() ");
return -1;
}
|
(2) |
*x = event_data.x;
*y = event_data.y;
/**
* イベントの種別が,パネルのリリースイベントか判定して結果を返す
**/
return (event_data.pressure == 0)? 0 : -1;
}
|
(3) |
タッチパネルのイベントとして渡されるデータフォーマットを格納するための構造体です.linuxのカーネルソースのdrivers/input/tsdev.cでも同じ構造体を使用しています.
タッチパネルのドライバからイベントのデータを読み込んで,先ほど定義した構造体に格納します.
イベントデータからタッチパネルのリリースイベントか判定し,戻り値で結果を返します.pressureの値が0だったときに,リリースイベントが発生したと判断しています.
次に,タッチされる部分に表示すべき画像を,指定位置への描画する部分です(リスト4).矩形画像データ用の構造体を作成して,タッチされる部分の数だけそのデータを準備し,フレームバッファ描画関数に渡す作りにしました.また,このデータはその後に説明するイベントの判別にも利用します.
●リスト4 指定位置への描画部分(ソースコード)
/* * タッチパネル上の画像選択時の実行処理種別 */ #define NO_EVENT 0 /* イベントなし*/ #define PREVIOUS_FILE 1 /* 次のファイルを表示*/ #define NEXT_FILE 2 /* 前のファイルを表示*/ |
|
/*
* 矩形画像データ用の構造体
*/
typedef struct _window_item
{
int x; /* 画像左上のx 座標*/
int y; /* 画像左上のy 座標*/
int width; /* 画像の幅*/
int height; /* 画像の高さ*/
int depth; /* 色深度*/
int event; /* タッチされたときのイベント種別*/
char filename[PATH_MAX]; /* 画像のファイル名*/
void* data; /* 描画データ*/
} window_item;
/* タッチされる部分に表示する画像(2つの矢印)をリストに登録*/
#define WINDOW_ITEM_NUM 2
window_item item_list[WINDOW_ITEM_NUM] =
{
{ 0, 260, 80, 80, 3, PREVIOUS_FILE, "pic/pre.bmp", NULL },
{ 720, 260, 80, 80, 3, NEXT_FILE, "pic/next.bmp", NULL }
};
|
(1) |
/*********************************************************************** * summary : 渡された矩形画像用データを指定位置に描画する * return : 成功0, 失敗-1 **********************************************************************/ int draw_window_item( window_item* draw_item, /* 矩形画像データ*/ void* fb_memory /* フレームバッファへのポインタ*/ ) |
|
{
int i;
void* base_addr; /* フレームバッファの書き始める位置用*/
/**
* 画像のサイズがフレームバッファを超えていないかチェック
**/
if((draw_item->x + draw_item->width) > DISPLAY_WIDTH) ||
(draw_item->y + draw_item->height) <= DISPLAY_HEIGHT)){
return -1;
}
|
(2) |
/**
* フレームバッファの描画開始位置を計算
**/
base_addr = fb_memory +
(DISPLAY_WIDTH * draw_item->y * COLOR_DEPTH) +
draw_item->x * COLOR_DEPTH;
/**
* 画像データをフレームバッファ内の表示位置にコピー
**/
for(i = 0 ; i < draw_item->height ; i++){
memcpy(
base_addr + (i * DISPLAY_WIDTH * COLOR_DEPTH),
draw_item->data + (i * draw_item->width * COLOR_DEPTH),
draw_item->width * COLOR_DEPTH
);
}
return 0;
}
|
(3) |
矩形画像のデータ用に構造体を定義し,次に実際に入力を受け付ける場所に描画する矩形画像のデータを作成しています.
今回のプログラムでは,次のファイルに進むための画像と前のファイルに戻るための画像の2つを用意し,800×600の画面領域のそれぞれ右端と左端,上下中央位置に描画されるようにデータを設定しています.eventという名称の構造体のメンバは,画像の領域が選択されたときに行うべき処理を判別するために使用されます.
描画する画像が,ウィンドウのサイズを超えないか判定する処理です.エラーにせずに,描画可能な範囲だけ描画するように変更するのも良いでしょう.
描画する画像のフレームバッファ内での開始位置を計算した後,1ラインごとに画像データをフレームバッファにコピーしています.
タッチパネルイベントが発生したときの,イベント判別処理部分です(リスト5).配列に登録されている矩形画像データから,イベントが発生した座標を含むものを検索し,一致した矩形画像データに登録されている実行処理を返す関数です.
●リスト5 イベント判定処理部分/*********************************************************************** * summary : 指定された座標にあるイベント用画像を調べて, * 登録されている実行処理を返す関数 * return : イベント種別 **********************************************************************/ int judge_event( int x, /* 選択されたx 座標*/ int y, /* 選択されたy 座標*/ window_item* item_list /* イベント用矩形画像データのリスト*/ ) { /** * 操作用の矩形画像分ループして,タッチパネルが押された位置にある画像を検索し * 見つかれば,画像に関連付けられている実行処理を返す * 見つからないときは,実行すべき処理がないことを返す **/ int i; for(i = 0 ; i < WINDOW_ITEM_NUM ; i++){ printf("item(%d) x : %d, y : %d, width : %d, height : %d\n", i, item_list[i].x, item_list[i].y, item_list[i].width, item_list[i].height); if(item_list[i].x <= x && x <= (item_list[i].x + item_list[i].width) && item_list[i].y <= y && y <= (item_list[i].y + item_list[i].height)) { return item_list[i].event; } } return NO_EVENT; }
プログラムをコンパイルしてArmadillo-9上で実行します.
カーネルの設定を変更したので,ビルドを行いカーネルを再作成します(図7).
ビルドに成功したら,netflash(2 章を参照)でカーネルイメージを内蔵のフラッシュメモリに書き込みます.
●図7 カーネルの更新$ make dep linux image 省略 $ ls images/ linux.bin linux.bin.gz romfs.img romfs.img.gz $
アウトオブツリーと呼ばれる,atmark-dist のソースツリー外でコンパイルする方法を使用します.任意の場所にプロジェクト用のディレクトリを作成し,Makefile とソースコードを取得します(図8).
●図8 Makefile とソースコードを取得する$ mkdir chapter4 $ cd chapter4 $ wget http://download.atmark-techno.com/misc/softwaredesign/chapter3/Makefile $ wget http://download.atmark-techno.com/misc/softwaredesign/chapter3/prog.c $ ls Makefile prog.c $
Makefileについて説明します(リスト6).
●リスト6 Makefile の編集### ### ROOTDIR に,atmark-dist のディレクトリを指定 ### ifndef ROOTDIR ROOTDIR=../atmark-dist endif ### ### atmark-dist のコンパイル設定をインクルード ### include $(ROOTDIR)/.config UCLINUX_BUILD_USER = 1 UCLINUX_BUILD_LIB = 1 LIBCDIR = $(CONFIG_LIBCDIR) include $(ROOTDIR)/config.arch ### ### JPEGライブラリを取り込むためにパスを追加 ### CFLAGS += -I$(ROOTDIR)/lib/libjpeg LDFLAGS += -L$(ROOTDIR)/lib/libjpeg LDLIBS += -ljpeg ### ### プログラム名とオブジェクトファイル名を指定する ### PROG = prog PROG_OBJS = prog.o ### ### 各ターゲットの記述 ### all: $(PROG) $(PROG): $(PROG_OBJS) $(CC) $(LDFLAGS) -o $@ $(PROG_OBJS) $(LDLIBS) clean: -rm -f $(PROG) $(PROG).elf $(PROG).gdb $(PROG_OBJS) %.o: %.c $(CC) -c $(CFLAGS) -o $@ $< ### ### atmark-dist のromfs/bin/ ディレクトリに実行ファイルをコピーするターゲット ### プログラムをユーザランドイメージに取り込む際に使用する ### romfs: $(ROMFSINST) /bin/$(EXEC)
ROOTDIRにはatmark-distのディレクトリを指定します.ダウンロードしたMakefile の記載と異なった場所にある場合は編集してください.
アウトオブツリーコンパイルのポイントは,Makefileでatmark-dist内のconfigファイルをインクルードしている点です.このファイルをインクルードすることで,使用するクロスコンパイラの指定や,ライブラリへのパスなどが適切に行われます.
今回はlibjpeg を使用するのでCFLSGS ,LDFLAGS,LDLIBSにlibjpeg に必要なオプションを追加しています.見慣れないromfsというターゲットがありますが,これはユーザランドイメージに実行ファイルを追加するためにあります.
ではいよいよビルドです.makeコマンドを実行してください(図9).atmark-dist を一度ビルドしておかないと,クロスコンパイラの指定や使用するライブラリなどが作成されず,正しくコンパイルできないので気を付けてください.
●図9 ビルド$ make arm-linux-gcc -c -O1 -I/usr/arm-linux/include -Dlinux -D__linux__ -Dunix -DEMBED -Wall -fno-common -I../atmark-dist -I../atmark-dist/lib/libjpeg -o prog.o prog.c arm-linux-gcc -g -L../atmark-dist/lib -L../atmark-dist/lib/libjpeg -o prog prog.o -lc -lgcc -ljpeg $
最終的には,作成したプログラムを含んだユーザランドイメージを作成し,内蔵のフラッシュメモリに書き込む必要がありますが,まだデバッグも済んでいないプログラムを毎回フラッシュメモリに書き込んでいては時間ばかりかかってしまいます.ここで重宝するのがNFSです.NFSが利用できる組み込み機器では,開発が効率よく進みます.
さっそくプロジェクトディレクトリをArmadillo-9からNFSでマウントします(開発用PCでNFSサーバを動作させる手順は割愛します).
開発用PC で,プロジェクトディレクトリを/etc/exportに追加し,NFSプロセスを再起動する
Armadillo-9で図10のようにコマンドを実行する(開発用PCのIPアドレスを192.168.0.100 としてい ます)
●図10 NFS を利用する# iptables -P INPUT ACCEPT # iptables -P OUTPUT ACCEPT # mount -o nolock -t nfs \ 192.168.0.100:/home/softwaredesign/chapter3 /mnt # ls /mnt Makefile prog prog.c prog.o #
プロジェクトのディレクトリにpicturesという名前のフォルダを作成し,800×600以下のサイズのJPEG ファイルを複数用意し(図11),このディレクトリを指定してプログラムを実行してみます.
●図11 複数のJPEG ファイルを用意する$ mkdir pictures $ cp ~/my_pictures/*.jpeg pictures $実行の前に,USB タッチパネル用のデバイスフ ァイルを作成します(図12).
# mkdir /dev/input # mknod /dev/input/event0 c 13 64
電源を切ると,再度デバイスファイルを作成する必要があるので注意してください(デバイスファイルを追加したユーザランドイメージの作成方法は5章で説明します).
図13のように実行します.どうでしょう,きちんと動作しましたか?
●図13 プログラムの実行# cd /mnt # ./prog pictures
ダウンロードサイトには,実際に動作するプログラムをイメージファイル化しておいてあるので,chapter3/linux.bin.gzとchapter3/romfs.img.gzをフラッシュメモリに書き込めば,実際の動作を確認することができます.
次章では音楽再生部分を作成します.
●組み込み機器の記憶装置 一般的なPCの記憶装置には、安価で大容量なハードディスクドライブが使用されますが、 ・サイズが大きい・可動部品なため、故障率が高い ・動作音がする ・アクセス速度が遅い などといった理由のため、組み込み用コンピュータでは敬遠されがちです。代わりに、 フラッシュメモリに代表される不揮発性メモリがしようされます。この点が、一般的なPCとの 大きな違いの1つです。 ●フラッシュメモリ フラッシュメモリは、電気的にデータを書き込めるのが特徴ですが、書き込み動作ではビットのデータを1から0に変更することしかできません。データを0から1にするには、一度消去という動作を行う必要があります。 一般的なフラッシュメモリでは、8Kバイトや64Kバイトといったサイズの複数のブロックに分割されており、ブロック単位で消去を行うことができます。消去を行うと、ブロック内のすべてのビットが1にリセットされ、再び書き込みを行うことができるようになります。 フラッシュメモリを使用するうえで注意しなければならないのは、消去可能な回数が約10万回であるということです。たとえば、10秒に1度データを保存するようなプログラムを書いた場合、およそ4ヶ月の連続稼動で書き込みができなくなってしまいます。 このような場合、同じブロックにデータを上書きするのではなく、複数のブロックを使い回すといった工夫が必要になります。なお、2章でも紹介した、フラッシュメモリ用に開発されたJFFS2というファイルシステムでは、この機能が盛り込まれています。 |
←前へ | ホーム | 次へ→ |